﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace QaaSimulation.Deprecated
{
    public class Battlefield : Control
    {
        public Timer timer;
        public Battlefield()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
            timer = new Timer();
            timer.Tick += new EventHandler(timer_Tick);
            timer.Interval = 40;
        }
        delegate void HandleTickCallback();

        private void HandleTick()
        {
            if (this.InvokeRequired)
            {
                HandleTickCallback d = new HandleTickCallback(HandleTick);
                this.Invoke(d);
            }
            else
            {
                Point p = this.PointToClient(Cursor.Position);
                for (int i = 0; i < QObjectCollection.AllObjects.Count; i++)
                {
                    QObject obj = QObjectCollection.AllObjects[i];
                    obj.Compute((timer.Interval / 1000.0f));
                    if (obj is QBullet)
                    {
                        ((QBullet)obj).TimeToLive -= timer.Interval / 1000.0f;
                        if (((QBullet)obj).TimeToLive <= 0.0f)
                        {
                            QObjectCollection.RemoveObject(obj);
                        }
                    }
                    if (obj is QShip)
                    {
                        if (((QShip)obj).Health <= 0.0f)
                        {
                            QObjectCollection.RemoveObject(obj);
                        }
                    }
                }
                Refresh();
            }
        }

        void timer_Tick(object sender, EventArgs e)
        {
            HandleTick();
        }

        protected override void OnMouseClick(MouseEventArgs e)
        {
            Random r = new Random();
            base.OnMouseClick(e);
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                QObjectCollection.AddObject(new QTower() { Position = e.Location, Size = new Size(20, 20), Range = 100f });
            }
            else if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                QObjectCollection.AddObject(new QShip() { Position = e.Location, Size = new Size(10, 10), Speed = new PointF(r.Next(100), 00) });
            }
            Refresh();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            foreach (QObject obj in QObjectCollection.AllObjects)
                obj.Paint(e.Graphics);
        }
    }

    public class QObjectCollection
    {
        private static List<QObject> m_AllObjects;
        public static List<QObject> AllObjects
        {
            get
            {
                if (m_AllObjects == null)
                    m_AllObjects = new List<QObject>();
                return m_AllObjects;
            }
            private set
            {
                m_AllObjects = value;
            }
        }

        private static List<QTower> m_AllTowers;
        public static List<QTower> AllTowers
        {
            get
            {
                if (m_AllTowers == null)
                    m_AllTowers = new List<QTower>();
                return m_AllTowers;
            }
            private set
            {
                m_AllTowers = value;
            }
        }

        private static List<QBullet> m_AllBullets;
        public static List<QBullet> AllBullets
        {
            get
            {
                if (m_AllBullets == null)
                    m_AllBullets = new List<QBullet>();
                return m_AllBullets;
            }
            private set
            {
                m_AllBullets = value;
            }
        }

        private static List<QShip> m_AllShips;
        public static List<QShip> AllShips
        {
            get
            {
                if (m_AllShips == null)
                    m_AllShips = new List<QShip>();
                return m_AllShips;
            }
            private set
            {
                m_AllShips = value;
            }
        }

        public static void AddObject(QObject obj)
        {
            AllObjects.Add(obj);
            if (obj is QTower)
            {
                AllTowers.Add(obj as QTower);
            }
            else if (obj is QBullet)
            {
                AllBullets.Add(obj as QBullet);
            }
            else if (obj is QShip)
            {
                AllShips.Add(obj as QShip);
            }
        }

        public static void RemoveObject(QObject obj)
        {
            AllObjects.Remove(obj);
            if (obj is QTower)
            {
                AllTowers.Remove(obj as QTower);
            }
            else if (obj is QBullet)
            {
                AllBullets.Remove(obj as QBullet);
            }
            else if (obj is QShip)
            {
                AllShips.Remove(obj as QShip);
            }
        }

    }

    public abstract class QObject
    {
        protected PointF m_Position;
        public PointF Position
        {
            get
            {
                return m_Position;
            }
            set
            {
                m_Position = value;
            }
        }

        protected SizeF m_Size;
        public SizeF Size
        {
            get
            {
                return m_Size;
            }
            set
            {
                m_Size = value;
            }
        }

        public abstract void Paint(Graphics g);

        public abstract void Compute(float elapsedTime);
    }

    public abstract class QMoveableObject : QObject
    {
        protected PointF m_Speed;
        public PointF Speed
        {
            get
            {
                return m_Speed;
            }
            set
            {
                m_Speed = value;
            }
        }
        protected PointF m_Direction;
        public PointF Direction
        {
            get
            {
                return m_Direction;
            }
            set
            {
                m_Direction = value;
            }
        }
        private void CalcPosition(float elapsedTime)
        {
            m_Position.X += m_Speed.X * elapsedTime;
            m_Position.Y += m_Speed.Y * elapsedTime;
        }

        public override void Paint(Graphics g)
        {

        }

        public override void Compute(float elapsedTime)
        {
            CalcPosition(elapsedTime);
        }
    }

    public class QBullet : QMoveableObject
    {
        public override void Paint(Graphics g)
        {
            g.FillRectangle(Brushes.Yellow, new RectangleF(m_Position.X - m_Size.Width / 2.0f, m_Position.Y - m_Size.Height / 2f, m_Size.Width, m_Size.Height));
        }

        public QBullet(PointF start, PointF target, float speed)
        {
            PointF direction = new PointF(target.X - start.X, target.Y - start.Y);
            float length = QFunctions.Length(direction);
            direction.X /= length;
            direction.Y /= length;
            direction.X *= speed;
            direction.Y *= speed;
            m_TimeToLive = length / speed;
            m_Position = start;
            m_Speed = direction;
            m_Size = new SizeF(5, 5);
            m_Damage = 5;
        }

        private float m_TimeToLive;
        public float TimeToLive
        {
            get
            {
                return m_TimeToLive;
            }
            set
            {
                m_TimeToLive = value;
            }
        }

        private float m_Damage = 2.5f;
        public float Damage
        {
            get
            {
                return m_Damage;
            }
            set
            {
                m_Damage = value;
            }
        }

        public override void Compute(float elapsedTime)
        {
            base.Compute(elapsedTime);
        }

    }

    public class QShip : QMoveableObject
    {
        public override void Paint(Graphics g)
        {
            g.FillEllipse(Brushes.Blue, new RectangleF(m_Position.X - m_Size.Width / 2.0f, m_Position.Y - m_Size.Height / 2f, m_Size.Width, m_Size.Height));
            g.DrawEllipse(Pens.Blue, new RectangleF(m_Position.X - m_Range, m_Position.Y - m_Range, m_Range * 2, m_Range * 2));
            g.FillRectangle(Brushes.Lime, m_Position.X - 20, m_Position.Y + m_Size.Height / 2f + 5, 40 * (m_Health / m_MaxHealth), 7);
            g.DrawRectangle(Pens.Black, m_Position.X - 20, m_Position.Y + m_Size.Height / 2f + 5, 40, 7);
        }

        private float m_Health = 100;
        public float Health
        {
            get
            {
                return m_Health;
            }
            set
            {
                m_Health = value;
            }
        }

        private float m_MaxHealth = 100;
        public float MaxHealth
        {
            get
            {
                return m_MaxHealth;
            }
            set
            {
                m_MaxHealth = value;
            }
        }

        public override void Compute(float elapsedTime)
        {
            base.Compute(elapsedTime);
            RectangleF actualRect = new RectangleF(m_Position.X - m_Size.Width / 2.0f, m_Position.Y - m_Size.Height / 2f, m_Size.Width, m_Size.Height);
            foreach (QBullet bullet in QObjectCollection.AllBullets)
            {
                if (actualRect.Contains(bullet.Position))
                {
                    m_Health -= bullet.Damage;
                    bullet.TimeToLive = 0;
                }
            }

            if (m_ActualRechargeTime > 0)
                m_ActualRechargeTime -= elapsedTime;
            if (m_ActualRechargeTime < 0)
                m_ActualRechargeTime = 0;
            if (m_ActualRechargeTime == 0)
                SearchForTarget();
        }

        protected float m_Range = 50;
        public float Range
        {
            get
            {
                return m_Range;
            }
            set
            {
                m_Range = value;
            }
        }

        protected float m_RechargeDelay = 0.25f;
        public float RechargeDelay
        {
            get
            {
                return m_RechargeDelay;
            }
            set
            {
                m_RechargeDelay = value;
            }
        }

        protected float m_ActualRechargeTime = 0;
        public float ActualRechargeTime
        {
            get
            {
                return m_ActualRechargeTime;
            }
            set
            {
                m_ActualRechargeTime = value;
            }
        }

        private bool Fire(QShip target)
        {
            PointF at = new PointF();
            if (computeHit(target, ref at))
            {
                float distance = QFunctions.Length(this.m_Position, at);
                if (distance < this.m_Range)
                {
                    PointF direction = new PointF(at.X - m_Position.X, at.Y - m_Position.Y);
                    float length = (float)Math.Sqrt(direction.X * direction.X + direction.Y * direction.Y);
                    direction.X /= length;
                    direction.Y /= length;
                    direction.X *= 20;
                    direction.Y *= 20;
                    QObjectCollection.AddObject(new QBullet(m_Position, at, 100f));
                    m_ActualRechargeTime += m_RechargeDelay;
                    return true;
                }
            }
            return false;
        }

        public void SearchForTarget()
        {
            foreach (QObject obj in QObjectCollection.AllShips)
            {
                if (obj is QShip && obj != this)
                {
                    float distance = QFunctions.Length(this.m_Position, obj.Position);
                    if (distance < this.m_Range)
                    {
                        if (Fire(obj as QShip))
                            return;
                    }
                }
            }
        }

        private bool computeHit(QShip target, ref PointF outHitPosition)
        {
            PointF shooterVelocity = new PointF(0, 0);
            PointF shooterPosition = this.m_Position;
            PointF targetPosition = target.Position;
            PointF targetVelocity = target.Speed;
            float projectileSpeed = 100f;
            // Relative Zielposition und -geschwindigkeit berechnen.
            PointF targetRelPosition = new PointF(targetPosition.X - shooterPosition.X, targetPosition.Y - shooterPosition.Y);
            PointF targetRelVelocity = new PointF(targetVelocity.X - shooterVelocity.X, targetVelocity.Y - shooterVelocity.Y);

            // Koeffizienten der biquadratischen Gleichung berechnen.
            float C = QFunctions.LengthSq(targetRelVelocity) - projectileSpeed * projectileSpeed;
            float D = 2 * QFunctions.Dot(targetRelPosition, targetRelVelocity);
            float E = QFunctions.LengthSq(targetRelPosition);

            // Reelle Lösungen der Gleichung bestimmen.
            // Wichtig: magnet::math::quarticSolve geht davon aus, dass A = 1 ist.
            // Also dividieren wir alle übrigen Koeffizienten durch A.
            float[] r = new float[2];
            int n = QFunctions.SolveQuadratic(C, D, E, ref r);

            //// Gibt es Lösungen?
            if (n == 0)
            {
                // Keine Lösung - das Ziel kann nicht getroffen werden.
                return false;
            }

            //// Die kleinste positive Lösung suchen.
            float smallestPositive = -1.0f;
            for (int i = 0; i < n; i++)
            {
                if (r[i] > 0.0 &&
                   (smallestPositive == -1.0 || r[i] < smallestPositive))
                {
                    smallestPositive = r[i];
                }
            }

            // Gab es überhaupt eine positive Lösung?
            if (smallestPositive == -1.0)
            {
                // Nein - kein Treffer möglich!
                return false;
            }

            // Wir übernehmen die gefundene Lösung.
            float t = smallestPositive;

            //// Ort des Treffers (relativ zum Schützen) berechnen.
            //const Vector hitRelPosition = targetRelPosition + t * targetRelVelocity;
            PointF hitRelPosition = new PointF(targetRelPosition.X + t * targetRelVelocity.X, targetRelPosition.Y + t * targetRelVelocity.Y);


            //// Richtung des Abschusses berechnen.
            //const Vector shootDir = (hitRelPosition - 0.5 * gravity * t * t) / (projectileSpeed * t);

            //// Gefundene Werte ausgeben.
            //outHitTime = t;
            outHitPosition.X = targetPosition.X + t * targetVelocity.X;
            outHitPosition.Y = targetPosition.Y + t * targetVelocity.Y;
            //outShootDir = shootDir;

            return true;
        }
    }

    public class QWeapon
    {
    }

    public class QTower : QObject
    {
        protected float m_Range;
        public float Range
        {
            get
            {
                return m_Range;
            }
            set
            {
                m_Range = value;
            }
        }

        protected float m_RechargeDelay = 0.25f;
        public float RechargeDelay
        {
            get
            {
                return m_RechargeDelay;
            }
            set
            {
                m_RechargeDelay = value;
            }
        }

        protected float m_ActualRechargeTime = 0;
        public float ActualRechargeTime
        {
            get
            {
                return m_ActualRechargeTime;
            }
            set
            {
                m_ActualRechargeTime = value;
            }
        }

        public override void Compute(float elapsedTime)
        {
            if (m_ActualRechargeTime > 0)
                m_ActualRechargeTime -= elapsedTime;
            if (m_ActualRechargeTime < 0)
                m_ActualRechargeTime = 0;
            if (m_ActualRechargeTime == 0)
                SearchForTarget();
        }

        public override void Paint(Graphics g)
        {
            g.FillRectangle(Brushes.Red, new RectangleF(m_Position.X - m_Size.Width / 2.0f, m_Position.Y - m_Size.Height / 2f, m_Size.Width, m_Size.Height));
            g.DrawEllipse(Pens.Red, new RectangleF(m_Position.X - m_Range, m_Position.Y - m_Range, m_Range * 2, m_Range * 2));
        }

        private bool Fire(QShip target)
        {
            PointF at = new PointF();
            if (computeHit(target, ref at))
            {
                float distance = QFunctions.Length(this.m_Position, at);
                if (distance < this.m_Range)
                {
                    PointF direction = new PointF(at.X - m_Position.X, at.Y - m_Position.Y);
                    float length = (float)Math.Sqrt(direction.X * direction.X + direction.Y * direction.Y);
                    direction.X /= length;
                    direction.Y /= length;
                    direction.X *= 20;
                    direction.Y *= 20;
                    QObjectCollection.AddObject(new QBullet(m_Position, at, 100f));
                    m_ActualRechargeTime += m_RechargeDelay;
                    return true;
                }
            }
            return false;
        }

        public void SearchForTarget()
        {
            foreach (QObject obj in QObjectCollection.AllShips)
            {
                if (obj is QShip)
                {
                    float distance = QFunctions.Length(this.m_Position, obj.Position);
                    if (distance < this.m_Range)
                    {
                        if (Fire(obj as QShip))
                            return;
                    }
                }
            }
        }

        private bool computeHit(QShip target, ref PointF outHitPosition)
        {
            PointF shooterVelocity = new PointF(0, 0);
            PointF shooterPosition = this.m_Position;
            PointF targetPosition = target.Position;
            PointF targetVelocity = target.Speed;
            float projectileSpeed = 100f;
            // Relative Zielposition und -geschwindigkeit berechnen.
            PointF targetRelPosition = new PointF(targetPosition.X - shooterPosition.X, targetPosition.Y - shooterPosition.Y);
            PointF targetRelVelocity = new PointF(targetVelocity.X - shooterVelocity.X, targetVelocity.Y - shooterVelocity.Y);

            // Koeffizienten der biquadratischen Gleichung berechnen.
            float C = QFunctions.LengthSq(targetRelVelocity) - projectileSpeed * projectileSpeed;
            float D = 2 * QFunctions.Dot(targetRelPosition, targetRelVelocity);
            float E = QFunctions.LengthSq(targetRelPosition);

            // Reelle Lösungen der Gleichung bestimmen.
            // Wichtig: magnet::math::quarticSolve geht davon aus, dass A = 1 ist.
            // Also dividieren wir alle übrigen Koeffizienten durch A.
            float[] r = new float[2];
            int n = QFunctions.SolveQuadratic(C, D, E, ref r);

            //// Gibt es Lösungen?
            if (n == 0)
            {
                // Keine Lösung - das Ziel kann nicht getroffen werden.
                return false;
            }

            //// Die kleinste positive Lösung suchen.
            float smallestPositive = -1.0f;
            for (int i = 0; i < n; i++)
            {
                if (r[i] > 0.0 &&
                   (smallestPositive == -1.0 || r[i] < smallestPositive))
                {
                    smallestPositive = r[i];
                }
            }

            // Gab es überhaupt eine positive Lösung?
            if (smallestPositive == -1.0)
            {
                // Nein - kein Treffer möglich!
                return false;
            }

            // Wir übernehmen die gefundene Lösung.
            float t = smallestPositive;

            //// Ort des Treffers (relativ zum Schützen) berechnen.
            //const Vector hitRelPosition = targetRelPosition + t * targetRelVelocity;
            PointF hitRelPosition = new PointF(targetRelPosition.X + t * targetRelVelocity.X, targetRelPosition.Y + t * targetRelVelocity.Y);


            //// Richtung des Abschusses berechnen.
            //const Vector shootDir = (hitRelPosition - 0.5 * gravity * t * t) / (projectileSpeed * t);

            //// Gefundene Werte ausgeben.
            //outHitTime = t;
            outHitPosition.X = targetPosition.X + t * targetVelocity.X;
            outHitPosition.Y = targetPosition.Y + t * targetVelocity.Y;
            //outShootDir = shootDir;

            return true;
        }
    }
}
